//
//  LGSNode.m
//  LogicSim
//
//  Created by Jeremy Pereira on 04/04/2012.
//  Copyright (c) 2012 Jeremy Pereira. All rights reserved.
//

#import "LGSNode.h"
#import "LGSEdge.h"
#import "LGSError.h"
#import "LGSModule.h"
#import "LGSConnectionContext.h"
#import "LGSName.h"

NSString* const LGS_NODE_TYPE_KEY = @"nodeType";
NSString* const LGS_NAME_KEY = @"name";
NSString* const LGS_LABEL_KEY = @"label";
NSString* const LGS_INPUTS_KEY = @"inputNames";
NSString* const LGS_OUTPUTS_KEY = @"outputNames";
NSString* const LGS_NODE_DESC_KEY = @"desc";

@interface LGSNode ()

+(bool) nodeTypeIsPrimitive: (NSString*) aNodeType;
+(NSDictionary*) nodeTypeDescriptorDictionary;

-(void) removeInputForName: (LGSName*) name;
-(void) removeOutputsForName: (LGSName*) name;
-(void) removeOutputForEdge: (LGSEdge*) theEdge;


@end

@implementation LGSNode
{
    int uniqueId;
    NSString* label;
    __weak LGSModule* module_;
    NSString* localisedDesc;
    NSMutableDictionary* inputs_;
    NSMutableDictionary* outputs_;
}

@synthesize inputs = inputs_, outputs = outputs_, name = name_, label, module = module_;

-(id) init
{
    return [self initWithName: [LGSName null]];
}

-(id) initWithName: (LGSName*) newName
{
    self = [super init];
    if (self != nil)
    {
        name_ = [newName nameWithoutConnection];
        inputs_ = [[NSMutableDictionary alloc] init];
        outputs_ = [[NSMutableDictionary alloc] init];
        [self setInputNames: [[self class] defaultInputNames]];
        [self setOutputNames: [[self class] defaultOutputNames]];
    }
    return self;
}

-(id) initWithNameString: (NSString*) newName
{
    return [self initWithName: [LGSName nameWithString: newName]];
}


-(id) copyWithZone: (NSZone*) zone
{
    LGSNode* ret = [(LGSNode*)[[self class] alloc] initWithName: [self name]];
    if (ret != nil)
    {
        [ret setLabel: [self label]];
        [ret setInputNames: [self inputNames]];
        [ret setOutputNames: [self outputNames]];
        [ret setDesc: [self desc]];
    }
    return ret;
}

#define NT_DESC(D,C) \
[NSDictionary dictionaryWithObjectsAndKeys:	\
				NSLocalizedString((D), @""), LGS_NODE_DESC_KEY,		\
                (C), LGS_NODE_TYPE_KEY,		\
				nil]

+(NSArray*) nodeTypeDescriptors
{
    static NSArray* nodeTypeDescriptors = nil;
    if (nodeTypeDescriptors == nil)
    {
        nodeTypeDescriptors = [NSArray arrayWithObjects:
                               NT_DESC(@"true", @"LGS1Const"),
                               NT_DESC(@"false", @"LGS0Const"),
                               NT_DESC(@"or gate", @"LGSOrGate"),
                               NT_DESC(@"and gate", @"LGSAndGate"),
                               NT_DESC(@"not gate", @"LGSNotGate"),
                               NT_DESC(@"xor gate", @"LGSXorGate"),
                               NT_DESC(@"module", @"LGSModule"),
                               NT_DESC(@"splitter", @"LGSSplitter"),
                               nil];
    }
    return nodeTypeDescriptors;
}


+(NSDictionary*) nodeTypeDescriptorDictionary 
{
    static NSMutableDictionary* descriptorDictionary = nil;
    if (descriptorDictionary == nil)
    {
        descriptorDictionary = [[NSMutableDictionary alloc] init];
        for (NSDictionary* dictionary in [self nodeTypeDescriptors])
        {
            [descriptorDictionary setObject: dictionary 
                                     forKey: [dictionary 
                                              objectForKey: LGS_NODE_TYPE_KEY]];
        }
    }
    return descriptorDictionary;
}

-(void) setName: (LGSName*) newName
{
    name_ = [newName copy];
	[[self module] changeNameForNode: self];
}


-(NSString*) desc
{
    if (localisedDesc == nil)
    {
        NSDictionary* descriptors = [[self class] nodeTypeDescriptorDictionary];
		NSDictionary* descriptor = [descriptors objectForKey: [self className]];
        localisedDesc = [descriptor objectForKey: LGS_NODE_DESC_KEY];
    }
    return localisedDesc;
}

+(LGSNode*) makeNodeFromDictionary: (NSDictionary*) dictionary 
                 connectionContext: connectionContext
                             error: (NSError* __autoreleasing *) error
{
    LGSNode* ret = nil;
    NSString* nodeType = [dictionary objectForKey: LGS_NODE_TYPE_KEY];
    if ([self nodeTypeIsPrimitive: nodeType])
    {
        ret = [[NSClassFromString(nodeType) alloc] init];
    }
    else 
    {
        LGSNode* nodeToCopy = [connectionContext nodeForName: [LGSName nameWithString:nodeType]];
        if (nodeToCopy != nil)
        {
            ret = [nodeToCopy copy];
        }
        else if (error != nil)
        {
            NSString* reason  
            = [NSString stringWithFormat: NSLocalizedString(@"Unknown node type '%@'", 
                                                            @"Error"),
               nodeType];
            
            *error = [LGSError deserialisationFailureReason: reason];
        }
    }
    if (ret != nil)
    {
        if (   ![ret overrideWithDictionary: dictionary
                                    context: connectionContext
                                      error: error]
            || ![connectionContext addNode: ret error: error])
        {
            ret = nil;
        }
    }
    return ret;
}

+(bool) nodeTypeIsPrimitive: (NSString*) aNodeType
{
    static NSSet* validNodeTypes = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        validNodeTypes = [NSSet setWithArray:
                          [[self nodeTypeDescriptors] valueForKey: LGS_NODE_TYPE_KEY]];
    });
    return [validNodeTypes containsObject: aNodeType];
}


-(NSString*) label
{
    if (label == nil)
    {
        label = [[self name] stringValue];
    }
    return label;
}

-(void) evaluateAffectedNodes: (NSMutableOrderedSet*) affectedNodes
{
    NSString* reason = [NSString stringWithFormat: @"%@ does not implement evaluateError:", [self className]];
    @throw [NSException exceptionWithName: LGS_METHOD_UNIMPLEMENTED 
                                   reason: reason 
                                 userInfo: nil];
}

-(TimeTick) gateDelay
{
    NSString* reason = [NSString stringWithFormat: @"%@ does not implement gateDelay", [self className]];
    @throw [NSException exceptionWithName: LGS_METHOD_UNIMPLEMENTED 
                                   reason: reason 
                                 userInfo: nil];
}

#pragma mark adding and removing connections

-(bool) addInput: (LGSEdge*) newInput forName: (LGSName*) inputName
{
    bool ret = false;
    if ([[self inputNames] containsObject: inputName])
    {
        [inputs_ setObject: newInput forKey: inputName];
        ret = true;
        [newInput setOutput: self];
        [newInput setOutputName: inputName];
    }
    return ret;
}

-(bool) addInput: (LGSEdge*) newInput forNameString: (NSString*) inputName
{
    return [self addInput: newInput
                  forName: [LGSName nameWithConnectionString: inputName]];
}


-(bool) addOutput: (LGSEdge*) newOutput forName: (LGSName*) outputName
{
    bool ret = false;
    NSMutableSet* outputsForName = [outputs_ objectForKey: outputName];
    if (outputsForName != nil)
    {
        [outputsForName addObject: newOutput];
        ret = true;
        [newOutput setInput: self];
        [newOutput setInputName: outputName];
    }
    return ret;
}


-(bool) addOutput: (LGSEdge*) newOutput forNameString: (NSString*) outputName
{
    return [self addOutput: newOutput
                  forName: [LGSName nameWithConnectionString: outputName]];
}


-(bool) connect: (LGSNode*) destination
           from: (LGSName*) outputName
             to: (LGSName*) inputName
{
    LGSEdge* edge = [[LGSEdge alloc] init];
    bool ret = false;
    /*
     *  We may need to back out the first part of connecting if the second part
     *  fails, so we need the previous input.
     */
    LGSEdge* oldInput = [destination input: inputName];
    if ([destination addInput: edge forName: inputName])
    {
        ret = [self addOutput: edge forName: outputName];
        if (!ret)
        {
            [destination addInput: oldInput forName: inputName];
        }
    }
    return ret;
}

-(bool) connect: (LGSNode*) destination
     fromString: (NSString*) outputNameString
       toString: (NSString*) inputNameString
{
    LGSName* inputName = [LGSName nameWithConnectionString: inputNameString];
    LGSName* outputName = [LGSName nameWithConnectionString: outputNameString];
    return [self connect: destination from: outputName to: inputName];
}


-(void) removeInputs
{
    NSArray* inputKeys = [inputs_ allKeys];
    for (LGSName* inputName in inputKeys)
    {
        [self removeInputForName: inputName];
    }
}

-(void) removeOutputs
{
    NSArray* outputKeys = [outputs_ allKeys];
    for (LGSName* outputName in outputKeys)
    {
        [self removeOutputsForName: outputName];
    }
}

-(void) removeInputForNameString: (NSString*) nameString
{
    [self removeInputForName: [LGSName nameWithConnectionString: nameString]];
}

-(void) removeInputForName: (LGSName*) name
{
    LGSEdge* theEdge = [self input: name];
    if (theEdge != nil && theEdge != [LGSEdge null])
    {
        [inputs_ setObject: [LGSEdge null] forKey: name];
        [theEdge setOutput: nil];
        [[theEdge input] removeOutputForEdge: theEdge];
    }
}

-(void) removeOutputsForNameString: (NSString*) name
{
    [self removeInputForName: [LGSName nameWithConnectionString: name]];
}

-(void) removeOutputsForName: (LGSName*) name
{
    NSMutableSet* outputEdges = [outputs_ objectForKey: name];
    NSSet* tempOutputEdges = [outputEdges copy];
    for (LGSEdge* edge in tempOutputEdges)
    {
        [self removeOutputForEdge: edge];
    }
    NSAssert2([outputEdges count] == 0,
              @"Failed to remove all output edges in %@.%@",
              self,
              name);
}

-(void) removeOutputForEdge: (LGSEdge*) theEdge
{
    NSMutableSet* edges = [outputs_ objectForKey: [theEdge inputName]];
    [edges removeObject: theEdge];
    if (theEdge != [LGSEdge null])
    {
        [theEdge setInput: nil];
        [[theEdge output] removeInputForName: [[theEdge outputName] nameWithoutNode]];
    }
}

#pragma mark Adding and removing connection names

-(void) setInputNamesFromStrings: (NSArray*) strings
{
    NSMutableArray* names = [[NSMutableArray alloc] init];
    for (NSString* string in strings)
    {
        [names addObject: [LGSName nameWithConnectionString: string]];
    }
    [self setInputNames: names];
}

-(void) setOutputNamesFromStrings: (NSArray*) strings
{
    NSMutableArray* names = [[NSMutableArray alloc] init];
    for (NSString* string in strings)
    {
        [names addObject: [LGSName nameWithConnectionString: string]];
    }
    [self setOutputNames: names];
}


-(bool) canModifyConnections
{
    return false;
}

-(void) addInputNameString: (NSString*) newInputNameString
{
    LGSName* newInputName = [[LGSName nameWithString: newInputNameString] nameWithoutNode];
    if ([self canModifyConnections])
    {
        
        if ([inputs_ objectForKey: newInputName] == nil)
        {
            [inputs_ setObject: [LGSEdge null] forKey: newInputName];
        }
        else
        {
            newInputName = [newInputName nameWithConnectionIndex: LGS_NAME_PLACEHOLDER_INDEX];
            [inputs_ setObject: [LGSEdge null] forKey: newInputName];
        }
    }
    else
    {
        NSString* desc = [NSString stringWithFormat:
                          @"You cannot modify connections of %@",
                          self];
        @throw [NSException exceptionWithName: @"OverrideException"
                                       reason: desc
                                     userInfo: nil];
        
    }
}
-(void) addOutputNameString: (NSString*) newOutputName
{
    if ([self canModifyConnections])
    {
        if ([outputs_ objectForKey: newOutputName] == nil)
        {
            [outputs_ setObject: [LGSEdge null] forKey: newOutputName];
        }
    }
    else
    {
        NSString* desc = [NSString stringWithFormat:
                          @"You cannot modify connections of %@",
                          self];
        @throw [NSException exceptionWithName: @"OverrideException"
                                       reason: desc
                                     userInfo: nil];
        
    }
}

-(bool) renameInputFromString: (NSString*) oldNameString toString:(NSString*) newNameString
{
    LGSName* oldName = [LGSName nameWithConnectionString: oldNameString];
    LGSName* newName = [LGSName nameWithConnectionString: newNameString];
    LGSEdge* inputEdge = [self input: oldName];
    bool canRename = inputEdge != nil && ![self hasInput: newName];
    if (canRename)
    {
        [inputs_ removeObjectForKey: oldName];
        [inputs_ setObject: inputEdge forKey: newName];
    }
    return canRename;
}

-(bool) renameOutputFromString: (NSString*) oldNameString
                      toString:(NSString*) newNameString
{
    LGSName* oldName = [LGSName nameWithConnectionString: oldNameString];
    LGSName* newName = [LGSName nameWithConnectionString: newNameString];
    NSSet* outputEdges = [self outputsForName: oldName];
    bool canRename = outputEdges != nil && ![self hasOutput: newName];
    if (canRename)
    {
        [outputs_ removeObjectForKey: oldName];
        [outputs_ setObject: outputEdges forKey: newName];
    }
    return canRename;
}


-(void) removeInputNameString: (NSString*) oldNameString
{
    LGSName* oldName = [LGSName nameWithConnectionString: oldNameString];
    [self removeInputForName: oldName];
    [inputs_ removeObjectForKey: oldName];
}

-(void) removeOutputNameString: (NSString*) oldNameString
{
    LGSName* oldName = [LGSName nameWithConnectionString: oldNameString];
    [self removeOutputsForName: oldName];
    [outputs_ removeObjectForKey: oldName];
}


-(void) setInputNames: (NSArray*)inputNames
{
    [inputs_ removeAllObjects];
    for (LGSName* aName in inputNames)
    {
        NSAssert1([aName isKindOfClass: [LGSName class]],
                  @"%@ should be an LGSName",
                  aName);
        [inputs_ setObject: [LGSEdge null] forKey: aName];
    }
}

-(void) setOutputNames: (NSArray*)outputNames
{
    [outputs_ removeAllObjects];
    for (LGSName* aName in outputNames)
    {
        NSAssert1([aName isKindOfClass: [LGSName class]],
                  @"%@ should be an LGSName",
                  aName);
        [outputs_ setObject: [[NSMutableSet alloc] init] forKey: aName];
    }
}

#pragma mark misc

-(NSString*) description
{
    if ([self name] != nil)
    {
        return [NSString stringWithFormat: @"<%@ %@>",
                                           [self className], 
                                           [self name]];
    }
    else 
    {
        return [NSString stringWithFormat: @"<%@ %p>",
                                           [self className], 
                                           (void*)self];
    }
}

-(NSDictionary*) jsonify
{
    NSMutableDictionary* ret = [[NSMutableDictionary alloc] init];
    [self addJsonKeysToDictionary: ret];
    return ret;
}

-(void) addJsonKeysToDictionary: (NSMutableDictionary*) dictionary
{
    [dictionary setObject: [self className] forKey: LGS_NODE_TYPE_KEY];
    [dictionary setObject: [[self name] stringValue] forKey: LGS_NAME_KEY];
}

-(bool) overrideWithDictionary: (NSDictionary*) dictionary
                       context: (LGSConnectionContext*) context
                         error: (NSError* __autoreleasing*) error
{
    NSString* aStringValue = [dictionary objectForKey: LGS_NAME_KEY];
    if (aStringValue != nil)
    {
        [self setName: [LGSName nameWithString: aStringValue]];
    }
    aStringValue = [dictionary objectForKey: LGS_LABEL_KEY];
    if (aStringValue != nil)
    {
        [self setLabel: aStringValue];
    }
    aStringValue = [dictionary objectForKey: LGS_NODE_DESC_KEY];
    if (aStringValue != nil)
    {
        [self setDesc: aStringValue];
    }
    return true;
}

-(NSArray*) inputNames
{
    NSArray* ret = [[[self inputs] allKeys] sortedArrayUsingComparator:
                    ^NSComparisonResult(id obj1, id obj2)
    {
        return [obj1 localizedCompare: obj2];
    }];
    return ret;
}

-(NSArray*) outputNames
{
    NSArray* ret = [[[self outputs] allKeys] sortedArrayUsingComparator:
                    ^NSComparisonResult(id obj1, id obj2)
                    {
                        return [obj1 localizedCompare: obj2];
                    }];
    return ret;
}

-(LGSEdge*) input: (NSString*) inputKey
{
    return [[self inputs] objectForKey: inputKey];
}

-(NSSet*) outputsForName: (NSString*) outputName
{
    return [[self outputs] objectForKey: outputName];
}


-(bool) hasInput: (NSString*)inputName
{
    return [[self inputs] objectForKey: inputName] != nil;
}

-(bool) hasOutput: (NSString*)outputName
{
    return [[self outputs] objectForKey: outputName] != nil;
}

+(NSArray*) defaultInputNames
{
    return [NSArray array];
}

+(NSArray*) defaultOutputNames
{
    return [NSArray array];
}

+(bool) createConnections: (NSDictionary*) connections
			 usingContext: (LGSConnectionContext*) context
                    error: (NSError* __autoreleasing*) error
{
    __block bool ret = true;
    __block NSError* myError = nil;
    
    [connections enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
    {
        LGSName* inputName = [LGSName nameWithString: key];
        LGSName* outputName = [LGSName nameWithString: obj];
        if ([inputName connectionPart] != nil && [outputName connectionPart] != nil)
        {
            LGSNode* fromNode = [context nodeForName: outputName];
            LGSNode* toNode = [context nodeForName: inputName];
            if (fromNode != nil && toNode != nil)
            {
                if (![fromNode connect: toNode
                                  from: [outputName nameWithoutNode]
                                    to: [inputName nameWithoutNode]])
                {
                    NSString* connectionDesc = [NSString stringWithFormat:
                                                @"%@ => %@",
                                                outputName,
                                                inputName];
                    *stop = YES;
                    ret = false;
                    myError = [LGSError invalidConnection: connectionDesc];
                }
            }
            else
            {
                NSString* connectionDesc = [NSString stringWithFormat:
                                            @"%@ to %@",
                                            outputName,
                                            inputName];
                *stop = YES;
                ret = false;
                myError = [LGSError invalidConnection: connectionDesc];
            }
        }
        else
        {
            NSString* connectionDesc = [NSString stringWithFormat: @"%@ to %@", obj, key];
            *stop = YES;
            ret = false;
            myError = [LGSError invalidConnection: connectionDesc];
        }
    }];
    if (!ret && error != nil)
    {
        *error = myError;
    }
    return ret;
}

-(bool) canHaveSubNodes
{
    return false;
}

-(size_t) subNodeCount
{
    return 0;
}

-(LGSNode*) nodeForName: (LGSName*) name
{
    NSString* reason = [NSString stringWithFormat: @"%@ cannot have subnodes", self];
    @throw [NSException exceptionWithName: @"SubNodesNotSupported"
                                   reason: reason
                                 userInfo: nil];
}

-(void) addNode: (LGSNode*) newNode
{
    NSString* reason = [NSString stringWithFormat: @"%@ cannot have subnodes", self];
    @throw [NSException exceptionWithName: @"SubNodesNotSupported"
                                   reason: reason
                                 userInfo: nil];    
}

-(void) removeNode: (LGSNode*) nodeToGo
{
    // Default case has no subnodes
}


-(NSArray*) nodeNames
{
    return [NSArray array];
}

@end
